#define PPU_DEBUG

#if PPU_DEBUG && (!DEBUG || !TRACE)
    #undef PPU_DEBUG
#endif

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Zsa.Emulation.Util;

namespace Zsa.Emulation.PPU
{
    /// <summary>
    /// The picture processing unit.
    /// </summary>
    public partial class PictureProcessingUnit
    {
        #region Fields
        /// <summary>
        /// The ppu memory manager.
        /// </summary>
        private PpuMemoryManager _MemoryManager;

        /// <summary>
        /// The cpu memory mapper.  Allows the cpu
        /// to access the ppu memory.
        /// </summary>
        private PpuCpuMemoryMapper _CpuMemoryMapper;

        /// <summary>
        /// The state of the ppu.
        /// </summary>
        private PpuState _CurrentState;

        /// <summary>
        /// The address to read or write from in vram
        /// </summary>
        private ushort _CurrentVRamAddress;

        /// <summary>
        /// Buffer to hold the vram address until both bytes are written
        /// </summary>
        private ushort _CurrentVRamAddressBuffer;

        /// <summary>
        /// The current spr ram address.
        /// </summary>
        private byte _CurrentSprRamAddress;
        
        /// <summary>
        /// Buffer for the last read byte.
        /// </summary>
        private byte _LastReadBuffer = 0;

        /// <summary>
        /// The sprite memory.
        /// </summary>
        private byte[] _SpriteMemory = new byte[256];

        /// <summary>
        /// The cartridge io.
        /// </summary>
        private Cartridge.CartridgeSlot _CartIO;

        

        #region Registers
        /// <summary>
        /// The first control register.
        /// </summary>
        private PPUControlRegisterOneFlags _RegisterControlOne;

        

        /// <summary>
        /// The ppu control register two.
        /// </summary>
        private PPUControlRegisterTwoFlags _RegisterControlTwo;



        /// <summary>
        /// The ppu status register.
        /// </summary>
        private PPUStatusRegister _RegisterStatus;


        /// <summary>
        /// The sprite memory address.
        /// </summary>
        private byte _RegisterSpriteMemoryAddress;

       

        /// <summary>
        /// The sprite memory data.
        /// </summary>
        private byte _RegisterSpriteMemoryData;

       
        /// <summary>
        /// The screen scroll offsets.
        /// </summary>
        private byte _VerticalScrollRegister;

        /// <summary>
        /// The horizontal scroll register.
        /// </summary>
        private byte _HorizontalScrollRegister;

        /// <summary>
        /// The ppu memory address.
        /// </summary>
        private byte _RegisterPpuMemoryAddress;

        /// <summary>
        /// The ppu memory data.
        /// </summary>
        private byte _RegisterPpuMemoryData;

        #endregion Registers

        #endregion Fields

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="T:PictureProcessingUnit"/> class.
        /// </summary>
        /// <param name="cartIO">The cart IO.</param>
        public PictureProcessingUnit(Cartridge.CartridgeSlot cartIO)
        {
            _MemoryManager = new PpuMemoryManager(cartIO);
            _CpuMemoryMapper = new PpuCpuMemoryMapper(this);
            _CartIO = cartIO;
            
        }
        #endregion Constructor

        #region Methods

        /// <summary>
        /// Inits this instance.
        /// </summary>
        public void Init()
        {
            _MemoryManager.Init();
            
        }
        #endregion Methods

        #region Properties

        #region Register State
        /// <summary>
        /// The name attribute that is used for the background.
        /// </summary>
        /// <value>The background name table.</value>
        public NameAttributeTable BackgroundNameTable
        {
            get
            {
                switch ( this._RegisterControlOne & PPUControlRegisterOneFlags.NameTableAddressBits)
                {
                    case PPUControlRegisterOneFlags.NameTableAddress_0x2000:
                    case PPUControlRegisterOneFlags.NameTableAddress_0x2800:
                        return PpuMemory.NameAttributeTableOne;

                        
                    case PPUControlRegisterOneFlags.NameTableAddress_0x2400:
                    case PPUControlRegisterOneFlags.NameTableAddress_0x2C00:

                        return PpuMemory.NameAttributeTableTwo;
                        
                        
                    
                }
                return _MemoryManager.NameAttributeTableOne;
            }
        }

        /// <summary>
        /// Gets the sprite pattern table.
        /// </summary>
        /// <value>The sprite pattern table.</value>
        public PatternTable SpritePatternTable
        {
            get
            {
                if ((_RegisterControlOne & PPUControlRegisterOneFlags.SpritePatternTableAddress) ==
                    PPUControlRegisterOneFlags.SpritePatternTableAddress )
                {
                    return _MemoryManager.PatternTableTwo;
                }
                return _MemoryManager.PatternTableOne;

            }
        }

        /// <summary>
        /// Gets the background pattern table.
        /// </summary>
        /// <value>The background pattern table.</value>
        public PatternTable BackgroundPatternTable
        {
            get
            {
                if ((_RegisterControlOne & PPUControlRegisterOneFlags.BackgroundPatternTableAddress) ==
                    PPUControlRegisterOneFlags.BackgroundPatternTableAddress)
                {
                    return _MemoryManager.PatternTableTwo;
                    //return _MemoryManager.PatternTableTwo;
                }
                return _MemoryManager.PatternTableOne;
                //return _MemoryManager.PatternTableOne;

            }
        }

        /// <summary>
        /// Gets the memory increment amount.
        /// </summary>
        /// <value>The memory increment amount.</value>
        public byte MemoryIncrementAmount
        {
            get
            {
                if ((_RegisterControlOne & PPUControlRegisterOneFlags.VerticalWriteFlag) ==
                    PPUControlRegisterOneFlags.VerticalWriteFlag)
                {
                    return 32;
                }
                return 1;
            }
        }

        /// <summary>
        /// Gets a value indicating whether [V blank enabled].
        /// </summary>
        /// <value><c>true</c> if [V blank enabled]; otherwise, <c>false</c>.</value>
        public bool NmiFlag
        {
            get
            {
                return (_RegisterControlOne & PPUControlRegisterOneFlags.NmiFlag) ==
                    PPUControlRegisterOneFlags.NmiFlag;
            }
        }

        /// <summary>
        /// Gets a value indicating whether [large sprite]. 16x8 sprites.
        /// Otherwise 8x8 sprite
        /// </summary>
        /// <value><c>true</c> if [large sprites]; otherwise, <c>false</c>.</value>
        public bool LargeSprites
        {
            get
            {
                return (_RegisterControlOne & PPUControlRegisterOneFlags.SpriteSize) ==
                    PPUControlRegisterOneFlags.SpriteSize;
            }
        }

        /// <summary>
        /// Gets the color of the background.
        /// </summary>
        /// <value>The color of the background.</value>
        public Color BackgroundColor
        {
            get
            {
                switch ((_RegisterControlTwo & PPUControlRegisterTwoFlags.BackgroundColorBits))
                {
                    case PPUControlRegisterTwoFlags.BackgroundColorBlack:
                        return Color.Black;
                    case PPUControlRegisterTwoFlags.BackgroundColorBlue:
                        return Color.Blue;
                    case PPUControlRegisterTwoFlags.BackgroundColorGreen:
                        return Color.Green;
                    case PPUControlRegisterTwoFlags.BackgroundColorRed:
                        return Color.Red;
                    default: 
                        return Color.Lime;
//                        throw new NotSupportedException();

                }
            }
        }



        /// <summary>
        /// Gets a value indicating whether this instance is monochrome.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is monochrome; otherwise, <c>false</c>.
        /// </value>
        public bool IsMonochrome
        {
            get
            {
                return (_RegisterControlTwo & PPUControlRegisterTwoFlags.MonochromeMode) ==
                    PPUControlRegisterTwoFlags.MonochromeMode;  
            }
        }

        /// <summary>
        /// Gets a value indicating whether [hide background].
        /// </summary>
        /// <value><c>true</c> if [hide background]; otherwise, <c>false</c>.</value>
        public bool BackgroundEnabled
        {
            get
            {
                return (_RegisterControlTwo & PPUControlRegisterTwoFlags.BackgroundEnabled) ==
                    PPUControlRegisterTwoFlags.BackgroundEnabled;  
            }
        }

        /// <summary>
        /// Gets a value indicating whether [hide sprites].
        /// </summary>
        /// <value><c>true</c> if [hide sprites]; otherwise, <c>false</c>.</value>
        public bool SpritesEnabled
        {
            get
            {
                return (_RegisterControlTwo & PPUControlRegisterTwoFlags.SpritesEnabled) ==
                    PPUControlRegisterTwoFlags.SpritesEnabled;
            }
        }

        /// <summary>
        /// Gets a value indicating whether [ignore writes].
        /// </summary>
        /// <value><c>true</c> if [ignore writes]; otherwise, <c>false</c>.</value>
        public bool IgnoreWrites
        {
            get
            {
                return (_RegisterStatus & PPUStatusRegister.DisableWrites) ==
                    PPUStatusRegister.DisableWrites; 
            }
        }

        /// <summary>
        /// Gets a value indicating whether [in VBlank state] .
        /// </summary>
        /// <value><c>true</c> if [in VBlank]; otherwise, <c>false</c>.</value>
        public bool InVBlank
        {
            get
            {
                return (_RegisterStatus & PPUStatusRegister.VBlankFlag) ==
                    PPUStatusRegister.VBlankFlag; 
            }
            set
            {
                if (value)
                {
                    _RegisterStatus |= PPUStatusRegister.VBlankFlag;
                }
                else
                {
                    if ((_RegisterStatus & PPUStatusRegister.VBlankFlag) ==
                    PPUStatusRegister.VBlankFlag)
                    {
                        _RegisterStatus ^= PPUStatusRegister.VBlankFlag;
                    }
                }
                
                
            }
        }

        #endregion Register State

        #region Internal Properties

        #region Registers
        /// <summary>
        /// Gets or sets the register control one.
        /// Address : 0x2000 RW
        /// </summary>
        /// <value>The register control one.</value>
        internal byte RegisterControlOne
        {
            get 
            { 
                return (byte)_RegisterControlOne; 
            }
            set 
            {
                #if PPU_DEBUG
                string _Out = string.Format("Writing Control Register One. New Value : {1}  Old Value : {0}",
                    _RegisterControlOne, (PPUControlRegisterOneFlags)value);
                TraceLogger.TraceInformation(TraceSubsystem.Ppu, _Out);
                #endif
                _RegisterControlOne = (PPUControlRegisterOneFlags)value; 
            }
        }

        /// <summary>
        /// Gets or sets the register control two.
        /// Address : 0x2001 RW
        /// </summary>
        /// <value>The register control two.</value>
        internal byte RegisterControlTwo
        {
            get 
            {
                return (byte)_RegisterControlTwo;
            }
            set 
            {
                #if PPU_DEBUG
                string _Out = string.Format("Writing Control Register Two. New Value : {1}  Old Value : {0}",
                    _RegisterControlTwo, (PPUControlRegisterTwoFlags)value);
                TraceLogger.TraceInformation(TraceSubsystem.Ppu, _Out);
                #endif
                _RegisterControlTwo = (PPUControlRegisterTwoFlags)value; 
            }
        }

        /// <summary>
        /// Gets the status register value.
        /// Address : 0x2002 R
        /// </summary>
        /// <value>The register status.</value>
        internal byte RegisterStatus
        {
            get 
            {

                // The current state.  Might need to do this.  Might need to reset first.
                PPUStatusRegister _Value = _RegisterStatus;
                // Check to see if the vblank active flag is set and remove if so.
                if ((_RegisterStatus & PPUStatusRegister.VBlankFlag) == PPUStatusRegister.VBlankFlag)
                {
                    _RegisterStatus ^= PPUStatusRegister.VBlankFlag;
                }
                #if PPU_DEBUG
                string _Out = string.Format("Reading status register. New Value : {0}  Old Value : {1}. Spr : {2} Vram : {3}",
                    _RegisterStatus, _Value,_CurrentSprRamAddress,_CurrentVRamAddress);
                TraceLogger.TraceInformation(TraceSubsystem.Ppu, _Out);
                #endif

                // Clear the addresses and the ppu state.
                _CurrentVRamAddress = 0;
                _CurrentSprRamAddress = 0;
                _CurrentState = PpuState.None;
                // Return the state before reseting the addresses and vblank.
                // This might not be correct.
                return (byte)_Value; 
            }
        }

        /// <summary>
        /// Sets the sprite memory address used to access spr-memory.
        /// Address : 0x2003 W
        /// </summary>
        /// <value>The register sprite memory address.</value>
        internal byte RegisterSpriteMemoryAddress
        {
            set 
            {
                #if PPU_DEBUG 
                LogEvent(PpuDebugEvents.RegisterSpriteAddressWrite,
                    "Setting Spr-Ram Address. New Value : {0}",_RegisterSpriteMemoryAddress);
                #endif

                _RegisterSpriteMemoryAddress = value; 
            }
        }

        /// <summary>
        /// Gets or sets the sprite data using the sprite memory address set
        /// by writing to RegisterSpriteMemoryAddress 0x2003.  Increments 
        /// after each read or write.
        /// Address : 0x2004 RW
        /// </summary>
        /// <value>The register sprite memory data.</value>
        internal byte RegisterSpriteMemoryData
        {
            get 
            {
                byte _Value =  _SpriteMemory[_CurrentSprRamAddress];
                _CurrentSprRamAddress++;
                return _Value;
            }
            set 
            {
                _SpriteMemory[_CurrentSprRamAddress] = value;
               _CurrentSprRamAddress++;
            }
        }

        /// <summary>
        /// Gets or sets the register screen scroll offsets.
        /// Address : 0x2005 R
        /// </summary>
        /// <value>The register screen scroll offsets.</value>
        internal byte RegisterScreenScrollOffsets
        {
            set {

                if ((_CurrentState & PpuState.WritingRAMAddress)
                    == PpuState.WritingRAMAddress)
                {
                    _HorizontalScrollRegister = value;
                    _CurrentState ^= PpuState.WritingRAMAddress;
                    #if PPU_DEBUG
                    LogEvent(PpuDebugEvents.RegisterScrollOffsetWrite,
                        "Setting Scroll Offset New Value : {0}  Old Value : {1}. ",
                        value, _HorizontalScrollRegister);
                    #endif
                }
                else
                {
                    if (value < 240)
                    {
                        _VerticalScrollRegister = value;
                        _CurrentState |= PpuState.WritingRAMAddress;
                        #if PPU_DEBUG
                        LogEvent(PpuDebugEvents.RegisterScrollOffsetWrite,
                            "Setting Scroll Offset New Value : {0}  Old Value : {1}. ",
                            value, _VerticalScrollRegister);
                        #endif
                    }
                }

                #if PPU_DEBUG
                LogEvent(PpuDebugEvents.RegisterScrollOffsetWrite,
                    "Setting Scroll Offset New Value : {0}  Old Value : {1}. ",
                    value, _VerticalScrollRegister);
                #endif
                
            }
        }

        /// <summary>
        /// Sets the register ppu memory address.
        /// Address : 0x2006 R
        /// </summary>
        /// <value>The register ppu memory address.</value>
        internal byte RegisterPpuMemoryAddress
        {
            set 
            {

                
                if ((_CurrentState & PpuState.WritingRAMAddress)
                    == PpuState.WritingRAMAddress)
                {

                    #if PPU_DEBUG
                    LogEvent(PpuDebugEvents.RegisterPpuAddressWrite,
                        "Setting Vram Address lo bytes. Value : {0}  New Adr : {1}. ",
                            value, _CurrentVRamAddress |= value);
                    #endif

                    _CurrentVRamAddressBuffer |= value;
                    _CurrentState ^= PpuState.WritingRAMAddress;
                    _CurrentVRamAddress = _CurrentVRamAddressBuffer;
                    
                }
                else
                {

                    #if PPU_DEBUG
                    LogEvent(PpuDebugEvents.RegisterPpuAddressWrite,
"Setting Vram Address hi bytes. Value : {0}  New Adr : {1}. ",
                        value, ((value & 0x3F) << 8));
                    #endif



                    _CurrentVRamAddressBuffer = (ushort)((value & 0x3F) << 8);
                    _CurrentState |= PpuState.WritingRAMAddress;
                }


            }
        }

        
        /// <summary>
        /// Gets or sets the register ppu memory data.
        /// Address : 0x2007 RW
        /// </summary>
        /// <value>The register ppu memory data.</value>
        internal byte RegisterPpuMemoryData
        {
            get 
            {
                #if PPU_DEBUG
                //TraceLogger.TraceInformation(TraceSubsystem.Ppu,
                //    "Read Vram data via IO port.  Value : {0} " +
                //    " A : {1} N : {2}",
                //    0, _CurrentVRamAddress, _CurrentVRamAddress + MemoryIncrementAmount
                //);
                #endif
                //int _Address = ((0x1f & _CurrentVRamAddress) * 32) +
                //    ((0x03E0 & _CurrentVRamAddress) >> 5) + 
                //    ((_CurrentVRamAddress & 0x3000) >> 12);

                int _Address = (_CurrentVRamAddress & 0x3FFF);
                //switch ( _CurrentVRamAddress & 0x0C00 )
                //{
                //    default:
                //        _Address += 0x2000;
                //        break;
                //}

                //if (_Address >= 0x3F00)
                //{
                //    return _MemoryManager.Read((int)_Address);
                //}

                byte _Value = _LastReadBuffer;
                _LastReadBuffer = _MemoryManager.Read((int)_Address);
             //   return _LastRead;
//                byte _Value = _MemoryManager.Read((int)_CurrentVRamAddress);

                //TraceLogger.TraceInformation(TraceSubsystem.Ppu,
                //    "Read Vram data via IO port.  Value : {0} " +
                //    " A : {1} N : {2}",
                //    0, _CurrentVRamAddress, _CurrentVRamAddress + MemoryIncrementAmount
                //);


                //if ((_CurrentVRamAddress & 15) == 15)
                //{
                //    _CurrentVRamAddress |= 512;
                //}
                //else
                //{
                //    _CurrentVRamAddress += 1;
                //}



                if ((_RegisterControlOne & PPUControlRegisterOneFlags.VerticalWriteFlag) ==
    PPUControlRegisterOneFlags.VerticalWriteFlag)
                {
                    _CurrentVRamAddress += 32;
                }
                else
                {
                    _CurrentVRamAddress += 1;
                }
                _CurrentVRamAddress &= 0x3fff;
                //_CurrentVRamAddress += MemoryIncrementAmount;
                return _Value;
            }
            set 
            {
                #if PPU_DEBUG
                //TraceLogger.TraceInformation(TraceSubsystem.Ppu,
                //    "Writing Vram data via IO port.  Value : {0} " +
                //    " A : {1} N : {2}",
                //    value, _CurrentVRamAddress, _CurrentVRamAddress + MemoryIncrementAmount
                //);
                #endif
                //int _Address = ((0x1f & _CurrentVRamAddress) * 32) +
                //    ((0x03E0 & _CurrentVRamAddress) >> 5) +
                //    ((_CurrentVRamAddress & 0x3000) >> 12);


                _MemoryManager.Write((int)_CurrentVRamAddress, value);


                if ((_RegisterControlOne & PPUControlRegisterOneFlags.VerticalWriteFlag) ==
                    PPUControlRegisterOneFlags.VerticalWriteFlag)
                {
                    _CurrentVRamAddress += 32;
                }
                else
                {
                    _CurrentVRamAddress += 1;
                }

                _CurrentVRamAddress &= 0x3fff;
                //_CurrentVRamAddress += MemoryIncrementAmount;
            }
        }

        

        #endregion Registers


        /// <summary>
        /// Gets the cpu memory mapper.
        /// </summary>
        /// <value>The cpu memory mapper.</value>
        internal PpuCpuMemoryMapper CpuMemoryMapper
        {
            get
            {
                return _CpuMemoryMapper;
            }
        }

        /// <summary>
        /// Gets the ppu memory.
        /// </summary>
        /// <value>The ppu memory.</value>
        internal PpuMemoryManager PpuMemory
        {
            get
            {
                return _MemoryManager;
            }
        }

        internal byte[] SpriteRam
        {
            get
            {
                return _SpriteMemory;
            }
            set
            {
                _SpriteMemory = value;
            }
        }


        #endregion Internal Properties
        #endregion Properties
    }
}
